﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Xml;
using Imtiaz;
using MapReduce.Utils;
using System.IO;

namespace MapReduce.Comm
{
    public static class SlaveManager
    {
        private static Semaphore mResourceLock = new Semaphore(1, 1);

        private static SortedList<String, String> mFreeSlaves = new SortedList<String, String>();
        private static SortedList<String, String> mBuzySlaves = new SortedList<String, String>();
        private static SortedList<String, String> mErrorSlaves = new SortedList<String, String>();
        private static uint mTotalSlaves = 0;
        
        public delegate void SlaveCallback(String pSlaveAddress);
        public delegate void InitCallback();

        private static ushort mPort;
        private static String mConfigXmlPath=null;
        private static InitCallback mInitCallback = null;

        const String IDEAL = "Ideal";
        const String IN_PROGRESS = "In Progress";
        public static ushort Port
        {
            get { return mPort; }
        }

        private static SortedList<String, String> ResetFree()
        {
            if (mFreeSlaves.Count == 0)
            {
                if (mBuzySlaves.Count == 0)
                {
                    throw new Exception("No Slaves available");
                }
                else
                {
                    //Reuse buzy slaves. Load them up!
                    mFreeSlaves = mBuzySlaves;
                    mBuzySlaves = new SortedList<string, string>();
                }

            }
            return mFreeSlaves; 
        }
        public static SortedList<String, String> Free
        {
            get {
                return ResetFree();
               
            }
        }
        
        public static SortedList<String, String> Buzy
        {
            get { return mBuzySlaves; }
        }
        public static SortedList<String, String> Error
        {
            get { return mErrorSlaves; }
        }
        
        public static void Init(
                        String pMasterAddress,
                        ushort pMasterPort,
                        ushort pSlavePort, 
                        String pSlavesXmlPath, 
                        String pConfigXmlPath, InitCallback pInitCallback)
        {

            //Start this master.
            Slave.Start(pMasterAddress, pMasterPort);

            //Start the web server
            MyWebServer web = new MyWebServer(pMasterAddress, U.DEFAULT_HTTP_PORT);

            mPort = pSlavePort;
            mConfigXmlPath = pConfigXmlPath;
            mInitCallback = pInitCallback;

            XmlDocument doc = new XmlDocument();
            doc.Load(pSlavesXmlPath);

            XmlNodeList nodes = doc.SelectNodes("Nodes/Node");

            //reset dictionary
            mFreeSlaves = new SortedList<String, String>();
            mBuzySlaves = new SortedList<String, String>();
            mErrorSlaves = new SortedList<String, String>();

            mTotalSlaves = (uint)nodes.Count;

            //COnsider all slaves as FREE intially. 
            //Then check their status by making async requests to all the slaves.
            //The slaves will be categorized in FREE, BUZY and ERROR groups.

            for (int i = 0; i < nodes.Count; i++)
            {
                XmlNode node = nodes[i];
                CheckSlave(node.InnerText);
            }//for i




        }//Init

        public static String GetNext()
        {
            ResetFree();

            String add;
            mResourceLock.WaitOne();
            {
                add = mFreeSlaves.Keys[0];
                mFreeSlaves.RemoveAt(0);
                mBuzySlaves[add] = DateTime.Now.ToString();
            }
            mResourceLock.Release();

            return add;
        }

        public static void ReleaseSlave(String pAddress)
        {
            mResourceLock.WaitOne();
            {
                mBuzySlaves.Remove(pAddress);
                mFreeSlaves[pAddress] = IDEAL;
            }
            mResourceLock.Release();
        }

        public static void ErrorSlave(String pAddress, String pMessage)
        {
            mResourceLock.WaitOne();
            {
                JobCallbackPool.ReleaseSlave(pAddress, mPort);
                mBuzySlaves.Remove(pAddress);
                mErrorSlaves[pAddress] = pMessage;

                Logger.LogError(pAddress, "ErrorSlave", pMessage);
            }
            mResourceLock.Release();
        }


        public static void PingSlave(String pSlaveAddress, JobRequest.Callback pCallback)
        {
            JobRequest ping = new JobRequest(pSlaveAddress, mPort);
            ping.ValidateConfig(U.MapPath(Path.Combine("xml", "Ping.xml")), pCallback);
        }//PingSlave

        private static void CheckSlave(String pSlaveAddress )
        {
            //Check validity of this slave
            JobRequest req = new JobRequest(pSlaveAddress, mPort);

            req.ValidateConfig(mConfigXmlPath, delegate(byte pStatus, byte[][] pData)
                {


                    if (pStatus == JobRequest.SUCCESS)
                    {
                        //Good slave
                        mFreeSlaves[pSlaveAddress] = IDEAL;
                    }
                    else
                    {
                        String strResponse = U.Convert(pData[0]).ToString();
                        //Bad Bad slave.
                        mErrorSlaves[pSlaveAddress] = strResponse;
                        Logger.LogError(pSlaveAddress, "CheckConfig", strResponse);
                    }

                    /*
                    if (strResponse == "1")
                    {
                        //Good slave
                        mFreeSlaves[pSlaveAddress] = FREE;
                    }
                    else
                    {
                        //Bad Bad slave.
                        mErrorSlaves[pSlaveAddress] = strResponse;
                        Logger.LogError(pSlaveAddress, "CheckConfig", strResponse);
                    }
                    */
                    //decrement the counter. Once it reaches zero, callback
                    mTotalSlaves--;
                    if (mTotalSlaves == 0) mInitCallback();

                });


        }//CheckSlave
        /*
        public static void GetNext(SlaveCallback pCallback)
        {
            if (mConfigXmlPath==null) throw new Exception("Config file not set");

            //Enter critical section
            mResourceLock.WaitOne();

            //if we've ran out of free slaves, post the busy slaves as free
            if (mFreeSlaves.Count == 0)
            {
                if (mBuzySlaves.Count == 0)
                {
                    throw new Exception("None of the slave nodes are ready.");

                }

                mFreeSlaves = mBuzySlaves;
                mBuzySlaves = new SortedList<string, string>();
            }


            Random rand = new Random();
            int i = rand.Next(0, mFreeSlaves.Count - 1);
            String slaveAddress = mFreeSlaves.Keys[i];
            mFreeSlaves.RemoveAt(i);

            //check validity of this slave
            JobRequest req = new JobRequest(slaveAddress, mPort);

            try
            {
                req.ValidateConfig(mConfigXmlPath, delegate(byte[][] pData)
                {

                    String strResponse = U.Convert(pData[0]).ToString();
                    if (strResponse == "1")
                    {
                        //Callback
                        pCallback(slaveAddress);

                        //Add this node to the 'BuzySlaves' list.
                        mBuzySlaves[slaveAddress] = BUSY;

                        mResourceLock.Release();
                    }
                    else
                    {
                        //This node failed
                        mErrorSlaves[slaveAddress] = strResponse;

                        mResourceLock.Release();

                        //Try to get another slave
                        GetNext(pCallback);
                    }


                });
            }
            catch (Exception ex)
            {
                //Something 
                mErrorSlaves[slaveAddress] = ex.Message;
                mResourceLock.Release();
            }

        }//GetNext
        

        public static void CheckOnSlaves(SlaveCallback pCallback)
        {
            String slaveAddress = mFreeSlaves.Keys[0];
            mFreeSlaves.RemoveAt(0);

            //check validity of this slave
            JobRequest req = new JobRequest(slaveAddress, mPort);

            try
            {
                req.ValidateConfig(mConfigXmlPath, delegate(byte[][] pData)
                {

                    String strResponse = U.Convert(pData[0]).ToString();
                    if (strResponse == "1")
                    {
                        //Callback
                        pCallback(slaveAddress);

                        //Add this node to the 'BuzySlaves' list.
                        mBuzySlaves[slaveAddress] = BUSY;

                        mResourceLock.Release();
                    }
                    else
                    {
                        //This node failed
                        mErrorSlaves[slaveAddress] = strResponse;

                        mResourceLock.Release();

                        //Try to get another slave
                        GetNext(pCallback);
                    }


                });
            }
            catch (Exception ex)
            {
                //Something 
                mErrorSlaves[slaveAddress] = ex.Message;
                mResourceLock.Release();
            }

        }//CheckOnSlaves
        */
    }//SlaveManager

}
